package com.gomyb.common.base;

import com.alibaba.fastjson.JSON;
import com.gomyb.common.page.PageParam;
import com.gomyb.common.page.PageResult;
import com.gomyb.common.page.QueryFilter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.*;
import javax.transaction.Transactional;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author <a href="mailto:hellohesir@gmail.com">Mr_He</a>
 */
@Log4j2
public abstract class BaseServiceImpl<T, ID extends Serializable> implements BaseService<T, ID> {

    protected BaseDao<T, ID> dao;

    public abstract void setDao(BaseDao<T, ID> dao);

    @Autowired
    private EntityManager em;

    public boolean delete(T t) {
        try {
            dao.delete(t);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public T get(ID id) {
        Optional<T> opt = dao.findById(id);
        return 0 == opt.hashCode() ? null : opt.get();
    }

    /**
     * 分页查询
     */
    @Override
    public PageResult<T> findPage(PageParam pageParam) {
        try {
            Map<String, String> dataMap = pageParam.getData();
            Specification<T> specification = getSpecification(dataMap);

            return gettPageResult(pageParam, specification);
        } catch (Exception e) {
            throw new BaseException("Paging query error...", e);
        }
    }

    /**
     * 返回结果集
     */
    private PageResult<T> gettPageResult(PageParam pageParam, Specification<T> specification) {
        PageRequest pageRequest = PageRequest.of(pageParam.getPage(), pageParam.getLimit(),
                new Sort(pageParam.getSort(), pageParam.getSortName()));

        Page<T> result = dao.findAll(specification, pageRequest);
        return new PageResult<T>(true, result.getTotalElements(), result.getContent());
    }

    /**
     * 条件查询对象
     * <p>
     * 连接查询暂只支持单连左外查询
     * <p>
     * 不建议使用连接查询
     */
    private Specification<T> getSpecification(Map<String, String> dataMap) {
        return (Specification<T>) (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>(dataMap.size());
            for (Map.Entry<String, String> entry : dataMap.entrySet()) {
                if (StringUtils.isEmpty(entry.getKey()) || null == entry.getValue() || "".equals(entry.getValue().trim())) {
                    continue;
                }
                String key = entry.getKey();
                String value = entry.getValue().trim();
                if (StringUtils.isNotEmpty(key)) {
                    if (key.contains("[")) {
                        /**
                         * 级联条件查询
                         * 需要在实体中 声明以下字段注解
                         *
                         *  @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH }, fetch = FetchType.EAGER)
                         *  @NotFound(action= NotFoundAction.IGNORE)
                         *  @JoinColumn(name = "category_id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
                         */
                        int left = key.indexOf("[");
                        int right = key.indexOf("]");
                        if (-1 == left || -1 == right) {
                            throw new BaseException("参数异常");
                        }
                        Join<T, T> join = root.join(key.substring(0, left), JoinType.LEFT);
                        key = key.substring(left + 1, right);
                        predicates.addAll(addCondition(join, criteriaBuilder, key, value));
                    } else {
                        predicates.addAll(addCondition(root, criteriaBuilder, key, value));
                    }
                }
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        };
    }

    private List<Predicate> addCondition(From<T, T> root, CriteriaBuilder criteriaBuilder, String key, String value) {
        List<Predicate> predicates = new ArrayList<>();
        int idx = key.indexOf("_");
        if (-1 == idx) {
            predicates.add(criteriaBuilder.equal(root.get(key), value));
        } else {
            String column = key.substring(0, idx);
            String condition = key.substring(idx + 1).toUpperCase();
            switch (condition) {
                case "EQ":
                    predicates.add(criteriaBuilder.equal(root.get(column), value));
                    break;
                case "NO_EQ":
                    predicates.add(criteriaBuilder.notEqual(root.get(column), value));
                    break;
                case "LIKE":
                    predicates.add(criteriaBuilder.like(root.get(column), "%" + value + "%"));
                    break;
                default:
                    log.error("cannot find condition...");
            }
        }
        return predicates;
    }

    @Override
    public List<T> findAll() {
        return dao.findAll();
    }

    @Override
    public List<T> findAll(QueryFilter filter) {
        Map<String, String> params = filter.getFilter();
        Specification<T> specification = getSpecification(params);
        return dao.findAll(specification);
    }

    @Transactional
    @Override
    public T save(T obj) {
        return dao.save(obj);
    }

    @Transactional
    @Override
    public Integer saveAll(List<T> datas) {
        List<T> ts = dao.saveAll(datas);
        return ts.size();
    }

    private String getClassName() {
        Class tClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return tClass.getName();
    }

    private Class getClasses() {
        return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @Override
    public int deleteAll(String ids) {
        try {
            String hql = "DELETE FROM " + getClassName() + " WHERE id IN (" + ids + ")";
            Query query = em.createQuery(hql);
            return query.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            em.close();
        }
        return 0;
    }


    /**
     * 自定义hql 查
     * 防sql注入攻击
     */
    public List<T> findByHql(String sql) {
        try {
            Query query = em.createQuery(sql);
            return query.getResultList();
        } finally {
            em.close();
        }
    }


    /**
     * 推荐
     * 自定义hql 分页查询
     * 只需要传入主体sql以及分页对象即可
     */
    public <N> PageResult<N> findPageByHql(String hql, PageParam pageParam) {
        try {
            hql = com.gomyb.utils.StringUtils.replaceSQLSign(hql);

            Query countQuery = em.createQuery(getCountSql(hql));
            Integer count = Integer.parseInt(countQuery.getSingleResult().toString());

            Query query = em.createQuery(hql);
            query.setFirstResult(pageParam.getOffset());
            query.setMaxResults(pageParam.getLimit());

            List resultList = query.getResultList();

            return new PageResult<N>(true, count.longValue(), resultList);
        } finally {
            em.close();
        }
    }

    /**
     * 增删用
     */
    public int executeSql(String sql) {
        try {
            Query countQuery = em.createNativeQuery(getCountSql(sql));
            return Integer.valueOf(countQuery.getSingleResult().toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 推荐
     * 自定义Sql查询
     */
    public <N> List<N> findBySql(String sql, Class<?> clazz) {
        try {
            Query query = em.createNativeQuery(sql);
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            List resultList = query.getResultList();

            //结果集转换对象
            return (List<N>) JSON.parseArray(JSON.toJSONString(resultList), clazz);
        } finally {
            em.close();
        }
    }

    /**
     * 推荐
     * 自定义Sql 分页查询
     * 只需要传入主体sql以及分页对象即可
     */
    public <N> PageResult<N> findPageBySql(String sql, PageParam pageParam, Class<?> resultClazz) {
        try {
            sql = com.gomyb.utils.StringUtils.replaceSQLSign(sql);

            Query countQuery = em.createNativeQuery(getCountSql(sql));
            Integer count = Integer.parseInt(countQuery.getSingleResult().toString());

            Query query = em.createNativeQuery(sql);
            query.setFirstResult(pageParam.getOffset());
            query.setMaxResults(pageParam.getLimit());
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            List resultList = query.getResultList();

            //结果集转换对象
            List<N> result = (List<N>) JSON.parseArray(JSON.toJSONString(resultList), resultClazz);
            return new PageResult<N>(true, count.longValue(), result);
        } finally {
            em.close();
        }
    }

    /**
     * 根据sql/hql生成count sql
     */
    private String getCountSql(String sql) {
        String right = sql;
        while (true) {
            int fromIdx = right.indexOf("from");
            if (-1 == fromIdx) {
                fromIdx = right.indexOf("FROM");
            }
            if (-1 != fromIdx) {
                right = right.substring(fromIdx + 4);
            } else {
                break;
            }
        }
        return "SELECT COUNT(1) FROM " + right;
    }

    /**
     * 自定义hql 改
     */
    @Transactional
    public int updateByHql(String sql) {
        try {
            Query query = em.createQuery(sql);
            return query.executeUpdate();
        } finally {
            em.close();
        }
    }
}
